Reading OPUS files

source

This code is modified from the original OPUSreader2 documentation vignettes/opusreader2_introduction.Rmd by Philip Baumann and Thomas Knecht. It reads OPUS binary files and extracts metadata and absorbance data, which can then be plotted.

Plots & Metadata

The main function, read_opus(), reads one or more OPUS files and returns a nested list of class list_opusreader2. Each list contains both the spectral data and metadata for each file. The dsn argument is the data source name. It can be a character vector of folder paths (to read files recursively) or specific OPUS file paths. Start by testing the read_opus function on your corrected spectra folder, and saving the output of the first file.

#load opusreader2 from github.com/spectral-cockpit
library(opusreader2)

#save the path to your corrected spectra
# # Mac path
# corr_spectra <- "/Users/adrianneseiden/Library/CloudStorage/Box-Box/Salk Institute Project/AKS Salk files/Adrianne_FTIRdata/Corrected_files" # nolint
#  # Windows path
# corr_spectra <- "C:/Users/adria/Box/Box-Box/Salk Institute Project/AKS Salk files/Adrianne_FTIRdata/Corrected_files" # nolint
# # Relative path
# corr_spectra <- "../Adrianne_FTIRdata/Corrected_files"

#save the data from your corrected files as 'data_test'
data_test <- read_opus(dsn = corr_spectra)
#check the names of the list
names(data_test)
>  [1] "DRIFTS_pot001_13Cwheat_wk0_root_250725_corr.0"        
>  [2] "DRIFTS_pot012_13Csoy_wk10_root_250827_corr.0"         
>  [3] "DRIFTS_pot029_13Cwheat_wk10_root_250827_corr.0"       
>  [4] "DRIFTS_pot029_13Cwheat_wk40_root_250827_corr.0"       
>  [5] "DRIFTS_pot030_13Csoy_wk10_root_250725_corr.0"         
>  [6] "DRIFTS_pot030_13Csoy_wk40_root_250827_corr.0"         
>  [7] "DRIFTS_pot032_13Cwheat_wk0_root_250725_corr.0"        
>  [8] "DRIFTS_pot047_13Crice_wk0_root_250725_corr.0"         
>  [9] "DRIFTS_pot050_13Csoy_wk0_root_250827_corr.0"          
> [10] "DRIFTS_pot052_13CnoPlant_wk30_root_250919_corr.0"     
> [11] "DRIFTS_pot052_13CnoPlant_wk40_root_250919_corr.0"     
> [12] "DRIFTS_pot053_13Cwheat_wk10_root_250725_corr.0"       
> [13] "DRIFTS_pot053_13Cwheat_wk40_root_250919_corr.0"       
> [14] "DRIFTS_pot055_13Crice_wk10_root_250725_corr.0"        
> [15] "DRIFTS_pot055_13Crice_wk40_root_250827_corr.0"        
> [16] "DRIFTS_pot055qmark_13Crice_wk10_root_250725_corr.0"   
> [17] "DRIFTS_pot056_12Csoy_wk10_root_250919_corr.0"         
> [18] "DRIFTS_pot062_13Csoy_wk0_root_250725_corr.0"          
> [19] "DRIFTS_pot079_13Crice_wk0_root_250725_corr.0"         
> [20] "DRIFTS_pot080_13Csoy_wk10_root_250725_corr.0"         
> [21] "DRIFTS_pot080_13Csoy_wk40_root_250919_corr.0"         
> [22] "DRIFTS_pot086_12Csoy_wk0_root_250919_corr.0"          
> [23] "DRIFTS_pot087_13Crice_wk10_root_250827_corr.0"        
> [24] "DRIFTS_pot087_13Crice_wk40_root_250919_corr.0"        
> [25] "DRIFTS_pot094_13Csoy_wk10_root_250725_corr.0"         
> [26] "DRIFTS_pot094_13Csoy_wk10_soil_250630_corr.0"         
> [27] "DRIFTS_pot094_13Csoy_wk40_root_250919_corr.0"         
> [28] "DRIFTS_pot103_13Crice_wk0_root_recNMR_250725_corr.0"  
> [29] "DRIFTS_pot103_13Crice_wk0_root_vial1_250725_corr.0"   
> [30] "DRIFTS_pot103_13Crice_wk0_root_vial2_250725_corr.0"   
> [31] "DRIFTS_pot103_13Crice_wk0_root_vial3_250725_corr.0"   
> [32] "DRIFTS_pot107_12Crice_wk0_root_really13_250725_corr.0"
> [33] "DRIFTS_pot109_13Cwheat_wk40_root_250919_corr.0"       
> [34] "DRIFTS_pot119_13Crice_wk10_root_250827_corr.0"        
> [35] "DRIFTS_pot119_13Crice_wk40_root_250919_corr.0"
# define 'meas_1' as the first element of the 'data_test' list
meas_1 <- data_test[[1]]

Next I defined a function, plotSpectrum, to plot the spectral data from a single sample (meas_1)

# data is a list containing OPUS file data, including absorbance and metadata.

plotSpectrum <- function(data) { # nolint: object_name_linter.
  ab_data <- data$ab
  if (!is.null(ab_data) &&
        !is.null(ab_data$wavenumbers) && !is.null(ab_data$data) &&
        is.numeric(ab_data$wavenumbers) && is.numeric(ab_data$data) &&
        length(ab_data$wavenumbers) == length(ab_data$data) &&
        all(is.finite(ab_data$wavenumbers)) && all(is.finite(ab_data$data))) {
    plot(
      ab_data$wavenumbers, ab_data$data, type = "l",
      xlab = "Wavenumber (cm⁻¹)", ylab = "Absorbance",
      main = data$basic_metadata$dsn_filename,
      xlim = rev(range(ab_data$wavenumbers))
    )
  } else {
    cat("Absorbance data not found or not valid in this file.\n")
    str(ab_data)
  }
}
plotSpectrum(meas_1)

I then defined the metadataTable function, to extract and display the a subset of the metadata stored in the data extracted by ‘read_opus’ in a table format.

To view all the available data categories, run ‘names(meas_1)’ in the console. To view the parameters within these categories use str(meas_1$category_name) where ‘category_name’ is ‘basic_metadata’, ‘optics’, etc.

The plotMetadata function combines the plot and table for a single spectrum.

plotMetadata <- function(data) { # nolint: object_name_linter.
  plotSpectrum(data)
  metadataTable(data)
}
plotMetadata(meas_1)

Metadata
Parameter Value
File Name DRIFTS_pot001_13Cwheat_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:18:39 GMT-7
Max Y 0.799607157707214
Min Y -0.0012896629050374
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56884765625
Experiment (method) SALK_soils_method.xpm

Combined plot and table (folder)

The following for loop runs plotMetadata on each spectrum in the data_test list, which contains all the spectra in the folder. It will plot each spectrum and print its metadata below.

for (i in seq_along(data_test)) {
  cat("#### Spectrum", i, ":", names(data_test)[i], "\n\n")
  data_i <- data_test[[i]]
  ab_data <- data_i$ab
  valid <- !is.null(ab_data) &&
    !is.null(ab_data$wavenumbers) && !is.null(ab_data$data) &&
    is.numeric(ab_data$wavenumbers) && is.numeric(ab_data$data) &&
    length(ab_data$wavenumbers) == length(ab_data$data) &&
    all(is.finite(ab_data$wavenumbers)) && all(is.finite(ab_data$data))
  if (valid) {
    plotSpectrum(data_i)
    print(metadataTable(data_i))
  } else {
    cat("Data not valid or missing for this spectrum.\n")
    str(ab_data)
  }
}

Spectrum 1 : DRIFTS_pot001_13Cwheat_wk0_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot001_13Cwheat_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:18:39 GMT-7
Max Y 0.799607157707214
Min Y -0.0012896629050374
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56884765625
Experiment (method) SALK_soils_method.xpm

Spectrum 2 : DRIFTS_pot012_13Csoy_wk10_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot012_13Csoy_wk10_root_250827_corr.0
Timestamp 2025-08-27 18:52:52 GMT-7
Max Y 0.797225415706635
Min Y -0.00604866677895188
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.560791015625
Experiment (method) SALK_soils_method.xpm

Spectrum 3 : DRIFTS_pot029_13Cwheat_wk10_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot029_13Cwheat_wk10_root_250827_corr.0
Timestamp 2025-08-27 18:53:45 GMT-7
Max Y 0.722125887870789
Min Y -0.00508248526602983
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.559814453125
Experiment (method) SALK_soils_method.xpm

Spectrum 4 : DRIFTS_pot029_13Cwheat_wk40_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot029_13Cwheat_wk40_root_250827_corr.0
Timestamp 2025-08-27 18:54:46 GMT-7
Max Y 0.828895390033722
Min Y -0.00468211621046066
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.560791015625
Experiment (method) SALK_soils_method.xpm

Spectrum 5 : DRIFTS_pot030_13Csoy_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot030_13Csoy_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:21:37 GMT-7
Max Y 0.84355878829956
Min Y -0.00326710939407349
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56689453125
Experiment (method) SALK_soils_method.xpm

Spectrum 6 : DRIFTS_pot030_13Csoy_wk40_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot030_13Csoy_wk40_root_250827_corr.0
Timestamp 2025-08-27 18:54:58 GMT-7
Max Y 0.721331775188446
Min Y -0.00639449199661613
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.559814453125
Experiment (method) SALK_soils_method.xpm

Spectrum 7 : DRIFTS_pot032_13Cwheat_wk0_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot032_13Cwheat_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:22:45 GMT-7
Max Y 0.909599244594574
Min Y -0.00415003532543778
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56982421875
Experiment (method) SALK_soils_method.xpm

Spectrum 8 : DRIFTS_pot047_13Crice_wk0_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot047_13Crice_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:23:56 GMT-7
Max Y 0.712926685810089
Min Y -0.00133463030215353
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 9 : DRIFTS_pot050_13Csoy_wk0_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot050_13Csoy_wk0_root_250827_corr.0
Timestamp 2025-08-27 18:55:10 GMT-7
Max Y 0.772085309028625
Min Y -0.00875790882855654
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.560791015625
Experiment (method) SALK_soils_method.xpm

Spectrum 10 : DRIFTS_pot052_13CnoPlant_wk30_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot052_13CnoPlant_wk30_root_250919_corr.0
Timestamp 2025-09-19 18:16:15 GMT-7
Max Y 0.77551531791687
Min Y -0.005828230176121
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.392822265625
Experiment (method) SALK_soils_method.xpm

Spectrum 11 : DRIFTS_pot052_13CnoPlant_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot052_13CnoPlant_wk40_root_250919_corr.0
Timestamp 2025-09-19 18:16:20 GMT-7
Max Y 0.813514113426208
Min Y -0.004625812638551
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.393798828125
Experiment (method) SALK_soils_method.xpm

Spectrum 12 : DRIFTS_pot053_13Cwheat_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot053_13Cwheat_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:24:39 GMT-7
Max Y 0.997280716896057
Min Y -0.00962971057742834
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 13 : DRIFTS_pot053_13Cwheat_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot053_13Cwheat_wk40_root_250919_corr.0
Timestamp 2025-09-19 15:59:23 GMT-7
Max Y 0.797653913497925
Min Y -0.00369235360994935
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.389892578125
Experiment (method) SALK_soils_method.xpm

Spectrum 14 : DRIFTS_pot055_13Crice_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot055_13Crice_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:26:35 GMT-7
Max Y 0.816974341869354
Min Y -0.00123318459372967
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56689453125
Experiment (method) SALK_soils_method.xpm

Spectrum 15 : DRIFTS_pot055_13Crice_wk40_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot055_13Crice_wk40_root_250827_corr.0
Timestamp 2025-08-27 18:55:22 GMT-7
Max Y 0.852853655815125
Min Y -0.00635257223621011
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56396484375
Experiment (method) SALK_soils_method.xpm

Spectrum 16 : DRIFTS_pot055qmark_13Crice_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot055qmark_13Crice_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:25:51 GMT-7
Max Y 0.907369911670685
Min Y -0.00234174402430654
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 17 : DRIFTS_pot056_12Csoy_wk10_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot056_12Csoy_wk10_root_250919_corr.0
Timestamp 2025-10-07 18:03:40 GMT-7
Max Y 0.615259230136871
Min Y -0.0116841280832887
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.393798828125
Experiment (method) SALK_soils_method.xpm

Spectrum 18 : DRIFTS_pot062_13Csoy_wk0_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot062_13Csoy_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:27:28 GMT-7
Max Y 0.687717080116272
Min Y -0.00538174575194716
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56884765625
Experiment (method) SALK_soils_method.xpm

Spectrum 19 : DRIFTS_pot079_13Crice_wk0_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot079_13Crice_wk0_root_250725_corr.0
Timestamp 2025-07-25 20:28:00 GMT-7
Max Y 0.816869914531708
Min Y -0.00257659493945539
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56787109375
Experiment (method) SALK_soils_method.xpm

Spectrum 20 : DRIFTS_pot080_13Csoy_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot080_13Csoy_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:28:56 GMT-7
Max Y 0.77625048160553
Min Y -0.00814440380781889
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56689453125
Experiment (method) SALK_soils_method.xpm

Spectrum 21 : DRIFTS_pot080_13Csoy_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot080_13Csoy_wk40_root_250919_corr.0
Timestamp 2025-09-19 16:42:23 GMT-7
Max Y 0.764196336269379
Min Y -0.00511131621897221
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.391845703125
Experiment (method) SALK_soils_method.xpm

Spectrum 22 : DRIFTS_pot086_12Csoy_wk0_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot086_12Csoy_wk0_root_250919_corr.0
Timestamp 2025-10-07 18:04:00 GMT-7
Max Y 0.540434181690216
Min Y -0.0042975265532732
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.386962890625
Experiment (method) SALK_soils_method.xpm

Spectrum 23 : DRIFTS_pot087_13Crice_wk10_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot087_13Crice_wk10_root_250827_corr.0
Timestamp 2025-08-27 18:55:34 GMT-7
Max Y 0.699629366397858
Min Y -0.00116329535376281
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.560791015625
Experiment (method) SALK_soils_method.xpm

Spectrum 24 : DRIFTS_pot087_13Crice_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot087_13Crice_wk40_root_250919_corr.0
Timestamp 2025-09-19 17:31:48 GMT-7
Max Y 0.879431128501892
Min Y -0.0040658856742084
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.392822265625
Experiment (method) SALK_soils_method.xpm

Spectrum 25 : DRIFTS_pot094_13Csoy_wk10_root_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot094_13Csoy_wk10_root_250725_corr.0
Timestamp 2025-07-25 20:29:09 GMT-7
Max Y 1.27848649024963
Min Y -0.00875786785036325
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56689453125
Experiment (method) SALK_soils_method.xpm

Spectrum 26 : DRIFTS_pot094_13Csoy_wk10_soil_250630_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot094_13Csoy_wk10_soil_250630_corr.0
Timestamp 2025-07-25 20:46:03 GMT-7
Max Y 0.821687042713165
Min Y -0.00153941253665835
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.454833984375
Experiment (method) SALK_soils_method.xpm

Spectrum 27 : DRIFTS_pot094_13Csoy_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot094_13Csoy_wk40_root_250919_corr.0
Timestamp 2025-09-19 16:42:30 GMT-7
Max Y 0.825236082077026
Min Y -0.00458295457065105
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.391845703125
Experiment (method) SALK_soils_method.xpm

Spectrum 28 : DRIFTS_pot103_13Crice_wk0_root_recNMR_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot103_13Crice_wk0_root_recNMR_250725_corr.0
Timestamp 2025-07-25 20:30:32 GMT-7
Max Y 0.836920917034149
Min Y -0.00138377456460148
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56982421875
Experiment (method) SALK_soils_method.xpm

Spectrum 29 : DRIFTS_pot103_13Crice_wk0_root_vial1_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot103_13Crice_wk0_root_vial1_250725_corr.0
Timestamp 2025-07-25 20:30:55 GMT-7
Max Y 0.826825261116028
Min Y -0.000596170837525278
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56787109375
Experiment (method) SALK_soils_method.xpm

Spectrum 30 : DRIFTS_pot103_13Crice_wk0_root_vial2_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot103_13Crice_wk0_root_vial2_250725_corr.0
Timestamp 2025-07-25 20:31:26 GMT-7
Max Y 0.984898567199707
Min Y -0.00565983727574348
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 31 : DRIFTS_pot103_13Crice_wk0_root_vial3_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot103_13Crice_wk0_root_vial3_250725_corr.0
Timestamp 2025-07-25 20:31:46 GMT-7
Max Y 0.983739078044891
Min Y -0.0026757272426039
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 32 : DRIFTS_pot107_12Crice_wk0_root_really13_250725_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot107_12Crice_wk0_root_really13_250725_corr.0
Timestamp 2025-07-25 20:32:00 GMT-7
Max Y 1.03206670284271
Min Y -0.00172662467230111
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.56591796875
Experiment (method) SALK_soils_method.xpm

Spectrum 33 : DRIFTS_pot109_13Cwheat_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot109_13Cwheat_wk40_root_250919_corr.0
Timestamp 2025-09-19 15:59:28 GMT-7
Max Y 0.75251305103302
Min Y -0.00450036907568574
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.386962890625
Experiment (method) SALK_soils_method.xpm

Spectrum 34 : DRIFTS_pot119_13Crice_wk10_root_250827_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot119_13Crice_wk10_root_250827_corr.0
Timestamp 2025-08-27 18:55:49 GMT-7
Max Y 0.808935940265656
Min Y -0.0116460844874382
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.561767578125
Experiment (method) SALK_soils_method.xpm

Spectrum 35 : DRIFTS_pot119_13Crice_wk40_root_250919_corr.0

Metadata
Parameter Value
File Name DRIFTS_pot119_13Crice_wk40_root_250919_corr.0
Timestamp 2025-09-19 17:31:43 GMT-7
Max Y 0.954932034015656
Min Y -0.00412484398111701
Aperture Setting 6 mm
Scanner Velocity 10.0S
Result Spectrum AB
Resolution 4
Sample Scans 400
End Frequency 400
Start Frequency 4000
Duration 364.390869140625
Experiment (method) SALK_soils_method.xpm

Stacking multiple spectra

Stacking re-runs

This code looks for files with the same sample number and stacks their spectra, to check whether reruns are consistent.

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  echo = TRUE,
  results = "markup",
  include = TRUE
)
library(opusreader2)
library(RColorBrewer)



data_test <- read_opus(dsn = corr_spectra)

# Helper to extract sample number from filename (e.g., "pot094")
extract_sample_number <- function(filename) {
  m <- regexpr("[a-z]{3}[0-9]+", filename)
  if (m[1] != -1) {
    regmatches(filename, m)
  } else {
    NA
  }
}

# Helper to extract sample type (e.g.,"soil", "root")
extract_sample_type <- function(filename) {
  m <- regexpr("soil|root", filename, ignore.case = TRUE)
  if (m[1] != -1) {
    tolower(regmatches(filename, m))
  } else {
    NA
  }
}

# Helper to extract timepoint (e.g., "wk0", "wk40")
extract_timepoint <- function(filename) {
  m <- regexpr("wk[0-9]+", filename, ignore.case = TRUE)
  if (m[1] != -1) {
    tolower(regmatches(filename, m))
  } else {
    NA
  }
}

# Group files by sample number, sample type, and timepoint
sample_numbers <- sapply(names(data_test), extract_sample_number)
sample_types <- sapply(names(data_test), extract_sample_type)
timepoints <- sapply(names(data_test), extract_timepoint)

# Create unique combinations of sample number, sample type, and timepoint
sample_combinations <- paste(sample_numbers, sample_types, timepoints, sep = "_")
unique_combinations <- unique(sample_combinations[!is.na(sample_numbers)
                                                  & !is.na(sample_types)
                                                  & !is.na(timepoints)])

# Plot stacked spectra for each unique combination
library(RColorBrewer)
for (combination in unique_combinations) {
  idx <- which(sample_combinations == combination)
  if (length(idx) > 1) {
    # Extract sample number, type, and timepoint from combination
    parts <- strsplit(combination, "_")[[1]]
    sample_num <- parts[1]
    sample_type <- parts[2]
    timepoint <- parts[3]

    spectra <- data_test[idx]
    colors <- brewer.pal(min(length(spectra), 8), "Set1")
    plot(NULL, xlim = rev(range(spectra[[1]]$ab$wavenumbers)),
         ylim = range(sapply(spectra, function(x) x$ab$data), na.rm = TRUE),
         xlab = "Wavenumber (cm⁻¹)", ylab = "Absorbance",
         main = paste("Stacked spectra for", sample_num, "-", sample_type, "-", timepoint),
         bty = "l")
    for (i in seq_along(spectra)) {
      ab_data <- spectra[[i]]$ab
      if (!is.null(ab_data) && !is.null(ab_data$wavenumbers) &&
            !is.null(ab_data$data)) {
        lines(ab_data$wavenumbers, ab_data$data, col = colors[i], lwd = 2)
      }
    }
    legend("topright",
           inset = c(-0.05, -0.02),
           legend = names(spectra),
           col = colors[seq_along(spectra)],
           lwd = 2,
           xpd = TRUE,
           bty = "n")
  }
}

Stacking by crop & time

This code stacks spectra of the same crop and timepoint.

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  echo = TRUE,
  results = "markup",
  include = TRUE
)
# Helper functions to extract crop and timepoint from filename
extract_crop <- function(filename) {
  m <- regexpr("rice|wheat|soy", filename, ignore.case = TRUE)
  if (m[1] != -1) tolower(regmatches(filename, m)) else NA
}
extract_time <- function(filename) {
  m <- regexpr("wk[0-9]+", filename, ignore.case = TRUE)
  if (m[1] != -1) tolower(regmatches(filename, m)) else NA
}
extract_sample_type <- function(filename) {
  m <- regexpr("root|soil", filename, ignore.case = TRUE)
  if (m[1] != -1) {
    tolower(regmatches(filename, m))
  } else {
    NA
  }
}
# Get crop and time for each file
crops <- sapply(names(data_test), extract_crop)
times <- sapply(names(data_test), extract_time)
types <- sapply(names(data_test), extract_sample_type)

# Unique crop-time combinations
combos <- na.omit(unique(paste(crops, times, types, sep = "_")))

library(RColorBrewer)
for (combo in combos) {
  idx <- which(paste(crops, times, types, sep = "_") == combo)
  if (length(idx) > 1) {
    spectra <- data_test[idx]
    colors <- brewer.pal(min(length(spectra), 8), "Set1")
    plot(NULL, xlim = rev(range(spectra[[1]]$ab$wavenumbers)),
         ylim = range(sapply(spectra, function(x) x$ab$data), na.rm = TRUE),
         xlab = "Wavenumber (cm⁻¹)", ylab = "Absorbance",
         main = paste("Stacked spectra for", combo),
         bty = "l")
    for (i in seq_along(spectra)) {
      ab_data <- spectra[[i]]$ab
      if (!is.null(ab_data) &&
            !is.null(ab_data$wavenumbers)
          && !is.null(ab_data$data)) {
        lines(ab_data$wavenumbers, ab_data$data, col = colors[i], lwd = 2)
      }
    }
    legend("topright",
           inset = c(-0.05, -0.02),
           legend = names(spectra),
           col = colors[seq_along(spectra)],
           lwd = 2,
           xpd = TRUE,
           bty = "n")
  }
}

Reading DPT files

This code is modified from code by Stephany Soledad Chacon, “Box> Salk Institute Project> Salk Data> FTIR_Intact_decomposition_pots> FTIR_analysis.Rmd”. Using data in the DPT format, we can extrapolate sample attributes from the file names, then normalize and visualize spectra across replicates, crops, and timepoints. The width of the line bounding the spectra represents the range of the data.

Importing FTIR data using readr

Define file path and import multiple files into one data frame

#> Files found in directory:
#>  [1] "DRIFTS_pot001_13Cwheat_wk0_root_250725.0.dpt"        
#>  [2] "DRIFTS_pot012_13Csoy_wk10_root_250827.0.dpt"         
#>  [3] "DRIFTS_pot029_13Cwheat_wk10_root_250827.0.dpt"       
#>  [4] "DRIFTS_pot029_13Cwheat_wk40_root_250827.3.dpt"       
#>  [5] "DRIFTS_pot030_13Csoy_wk10_root_250725.0.dpt"         
#>  [6] "DRIFTS_pot030_13Csoy_wk40_root_250827.0.dpt"         
#>  [7] "DRIFTS_pot032_13Cwheat_wk0_root_250725.0.dpt"        
#>  [8] "DRIFTS_pot047_13Crice_wk0_root_250725.0.dpt"         
#>  [9] "DRIFTS_pot050_13Csoy_wk0_root_250827.0.dpt"          
#> [10] "DRIFTS_pot052_13CnoPlant_wk30_root_250919.0.dpt"     
#> [11] "DRIFTS_pot052_13CnoPlant_wk40_root_250919.0.dpt"     
#> [12] "DRIFTS_pot053_13Cwheat_wk10_root_250725.0.dpt"       
#> [13] "DRIFTS_pot053_13Cwheat_wk40_root_250919.0.dpt"       
#> [14] "DRIFTS_pot055_13Crice_wk10_root_250725.0.dpt"        
#> [15] "DRIFTS_pot055_13Crice_wk10_root_qmark_250725.0.dpt"  
#> [16] "DRIFTS_pot055_13Crice_wk40_root_250827.0.dpt"        
#> [17] "DRIFTS_pot056_12Csoy_wk10_root_250919.0"             
#> [18] "DRIFTS_pot062_13Csoy_wk0_root_250725.0.dpt"          
#> [19] "DRIFTS_pot079_13Crice_wk0_root_250725.0.dpt"         
#> [20] "DRIFTS_pot080_13Csoy_wk10_root_250725.0.dpt"         
#> [21] "DRIFTS_pot080_13Csoy_wk40_root_250919.0.dpt"         
#> [22] "DRIFTS_pot086_12Csoy_wk0_root_250919.0"              
#> [23] "DRIFTS_pot087_13Crice_wk10_root_250827.0.dpt"        
#> [24] "DRIFTS_pot087_13Crice_wk40_root_250919.0.dpt"        
#> [25] "DRIFTS_pot094_13Csoy_wk10_root_250725.0.dpt"         
#> [26] "DRIFTS_pot094_13Csoy_wk10_soil_250630.0.dpt"         
#> [27] "DRIFTS_pot094_13Csoy_wk40_root_250919.0.dpt"         
#> [28] "DRIFTS_pot103_13Crice_wk0_root_recNMR_250725.0.dpt"  
#> [29] "DRIFTS_pot103_13Crice_wk0_root_vial1_250725.0.dpt"   
#> [30] "DRIFTS_pot103_13Crice_wk0_root_vial2_250725.2.dpt"   
#> [31] "DRIFTS_pot103_13Crice_wk0_root_vial3_250725.0.dpt"   
#> [32] "DRIFTS_pot107_12Crice_wk0_root_really13_250725.0.dpt"
#> [33] "DRIFTS_pot109_13Cwheat_wk40_root_250919.0.dpt"       
#> [34] "DRIFTS_pot119_13Crice_wk10_root_250827.0.dpt"        
#> [35] "DRIFTS_pot119_13Crice_wk40_root_250919.0.dpt"
#> Number of .dpt files: 33

Extract sample info from filename

Using the long format dataframe dpt_data format example: DRIFTS_pot094_13Csoy_wk10_soil_250630.0.dpt

#> Column filenames: wavenumber absorbance filename
#> [1] "pot"
#>  [1] "001" "012" "029" "030" "032" "047" "050" "052" "053" "055" "062" "080"
#> [13] "087" "094" "103" "107" "109" "119"
#> [1] 13 12
#> [1] "wheat"   "soy"     "rice"    "noPlant"
#> [1] "wk0"  "wk10" "wk40" "wk30"
#> [1] "root" "soil"
#> [1] "250725" "250827" "250919" "250630"
#> [1] NA         "qmark"    "vial2"    "vial3"    "really13"
#> # A tibble: 6 × 11
#>   wavenumber absorbance filename      source ID    isotope crop  timepoint type 
#>        <dbl>      <dbl> <chr>         <chr>  <chr>   <dbl> <chr> <chr>     <chr>
#> 1      3998.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> 2      3997.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> 3      3995.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> 4      3994.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> 5      3993.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> 6      3991.     0.0115 DRIFTS_pot00… pot    001        13 wheat wk0       root 
#> # ℹ 2 more variables: run_date <chr>, notes <chr>

Normalize absorbance data

For each file, create a normalized absorbance column (nabs) by dividing each absorbance value by the maximum absorbance for that file.

Plotting by timepoint and type

These plots are of normalized absorbance values. The thickness of the opaque line represents the range of the data. Dotted lines at specific wavenumbers indicate key absorption features.

Week 0

FALSE [1] "001" "032" "050" "062" "103" "107"

Week 10

#> [1] "012" "029" "030" "053" "055" "080" "087" "119"

#> [1] "094"

Week 40

#>  [1] "029" "030" "052" "053" "055" "080" "087" "094" "109" "119"

Plotting by crop

These plots are of normalized absorbance values. The thickness of the line represents the range of the data. Dotted lines at specific wavenumbers indicate key absorption features.

  • 2900 cm-1 ← aliphatic (alkanes)
  • 1730 - 1650 cm-1 ← carbonyl; C=O stretch (carbonyl, carboxylic acids, esters, ketones, etc.)
  • 1500 cm-1 ← aromatic

I’m not sure if these are the best areas to highlight, but I kept what Stephany did.

Wheat Roots

#> [1] "001" "029" "032" "053" "109"

rice roots

#> [1] "055" "087" "103" "107" "119"

soy roots

#> [1] "012" "030" "050" "062" "080"